Skip to content

Fix Android issue with isUserInteraction always returning true when location puck is pulsing#4222

Open
KijongHan wants to merge 3 commits into
rnmapbox:mainfrom
KijongHan:fix/issue-with-isUserInteraction-always-returning-true-when-location-puck-is-pulsing
Open

Fix Android issue with isUserInteraction always returning true when location puck is pulsing#4222
KijongHan wants to merge 3 commits into
rnmapbox:mainfrom
KijongHan:fix/issue-with-isUserInteraction-always-returning-true-when-location-puck-is-pulsing

Conversation

@KijongHan

Copy link
Copy Markdown
Contributor

Description

Fixes #3805

Fixes Android bug where onRegionIsChanging's properties.isUserInteraction was stuck true whenever <LocationPuck pulsing="default" /> was mounted, even if there were no user action. Root cause: the LocationComponent's pulsing animator keeps the map perpetually non-idle (as reported here), so Mapbox's MapIdle event never fires.

This means that CameraChangeTracker never cleared user gesture state as addOnMapIdleListener never fired if that <LocationPuck /> component was mounted. Fix was to use the new custom MapSteady event already introduced here #4074

Changes

  • Update CameraChangeTracker and replace single reason: CameraChangeReason field with reasonByGesture: Map<MapGestureType, CameraChangeReason> so concurrent move/scale/rotate gestures don't cause race conditions.
  • Decouple MapSteadyDetector from RNMBXCameraGestureObserver and use this custom event from RNMBXMapView to drive clearing the CameraChangeTracker state which corrects the downstream propagation to properties.isUserInteraction
  • Minor refactor so MapGestureType is promoted to shared enum

Checklist

  • I've read CONTRIBUTING.md
  • I updated the doc/other generated code with running yarn generate in the root folder
  • I have tested the new feature on /example app.
    • In V11 mode/ios (no, but shouldn't be affected)
    • In New Architecture mode/ios (no, but shouldn't be affected)
    • In V11 mode/android
    • In New Architecture mode/android
  • I added/updated a sample - if a new feature was implemented (/example)

Component to reproduce the issue you're fixing

Just pasting the same repro code snippet provided by the original issue

import { useEffect, useRef } from 'react';
import {
    MapView,
    ShapeSource,
    LineLayer,
    Camera,
    LocationPuck,
} from '@rnmapbox/maps';
import type { CameraRef } from '../../../src/components/Camera';

const aLine = {
    type: 'LineString',
    coordinates: [
        [-74.00597, 40.71427],
        [-74.00697, 40.71527],
    ],
}

const BugReportExample = () => {

    const mapCameraRef = useRef<CameraRef>(null)
    const locationPuck = true // Set this to false to hide the location puck. The issue only presents itself when the locationPuck is shown (and more specifically when this is set pulsing={"default"})

    useEffect(() => {
        // Re-center the map every 5 seconds. When this is done, isUserInteraction should log as false (but it doesn't when locationPuck is true)
        const interval = setInterval(() => {
            mapCameraRef.current?.setCamera({
                centerCoordinate: [-74.00597, 40.71427],
                animationDuration: 1000,
                animationMode: 'linearTo',
            })
        }, 5_000)

        return () => {
            clearInterval(interval)
        }

    }, [])

    const regionIsChanging = (region: any) => {
        console.log("isUserInteraction:", region.properties.isUserInteraction)
    }

    return (
        <MapView style={{ flex: 1 }} onRegionIsChanging={regionIsChanging}>
            <Camera ref={mapCameraRef} centerCoordinate={[-74.00597, 40.71427]} zoomLevel={14} />
            <ShapeSource id="idStreetLayer" shape={aLine as any}>
                <LineLayer id="idStreetLayer" />
            </ShapeSource>
            {locationPuck ? <LocationPuck puckBearingEnabled={true} puckBearing="heading" pulsing={"default"} /> : null}
        </MapView>
    )
}

export default BugReportExample;

KijongHan added 3 commits June 2, 2026 10:03
… gestures being tracked that could lead to race conditions
…t map steady event detection is decoupled and can be minimally reused from RNMBXMapView
…ent kotlin components

- refactor MapSteadyDetector so that it uses enum instead of magic strings
@KijongHan
KijongHan temporarily deployed to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Inactive
@KijongHan
KijongHan had a problem deploying to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Failure
@KijongHan
KijongHan temporarily deployed to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Inactive
@KijongHan KijongHan changed the title Fix issue with isUserInteraction always returning true when location puck is pulsing Fix Android issue with isUserInteraction always returning true when location puck is pulsing Jun 1, 2026
@mfazekas

Copy link
Copy Markdown
Contributor

@KijongHan thanks much for the PR.

I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

@KijongHan

Copy link
Copy Markdown
Contributor Author

@KijongHan thanks much for the PR.

I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

thanks for reviewing these changes @mfazekas !

Just wanted to get some clarification on your suggestion. Do you mean deprecate onRegionIsChanging or onMapSteady?

If you mean the former, do you mean that we want to keep the underlying onRegionIsChanging event listener in CameraGestureObserver for internal logic but deprecate it from the javascript/React code?

@mfazekas

Copy link
Copy Markdown
Contributor

@KijongHan thanks much for the PR.
I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

thanks for reviewing these changes @mfazekas !

Just wanted to get some clarification on your suggestion. Do you mean deprecate onRegionIsChanging or onMapSteady?

If you mean the former, do you mean that we want to keep the underlying onRegionIsChanging event listener in CameraGestureObserver for internal logic but deprecate it from the javascript/React code?

To clarify: deprecate onRegionIsChanging (the MapView event), not onMapSteady.

RNMBXMapView is already too complicated and the long-term plan is to split it into components — so I'd rather move code out of MapView, not into it. Concretely: CameraGestureObserver gains an onRegionIsChanging-like event (say onCameraChange) reporting isUserInteraction from its own gesture/animator tracking. Since it doesn't depend on MapIdle, it's immune to the pulsing puck issue. The MapView event stays as-is natively and gets deprecated in the JS API, so #3805 is addressed by migrating to the new event. Extracting MapSteadyDetector as a helper is fine — it should just stay owned by CameraGestureObserver, and the CameraChangeTracker changes wouldn't be needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: isUserInteraction is always true when locationPuck is shown on Android

2 participants